// linux提供的头文件
#include <cerrno>
#include <unistd.h>
#include <fcntl.h>

// c++库文件
#include <cstring>
#include <streambuf>

// 自定义头文件
#include "serial_port.h"

int CSerialPort::open(const char *port, EBaudRate baud, EDataBits data, EParity pari, EStopBits stop,
                      EFlowControl flow, int time)
{
    // 创建串口
    this->handle_ = ::open(port, O_RDWR | O_NOCTTY);
    if (this->handle_ < 0)
    {
        // 发生错误，抛出异常
        throw std::system_error(std::error_code(errno, std::generic_category()), "Failed to open port...");
    }
    // 设置工作模式
    this->set_normal_mode();
    // 设置波特率
    this->set_baud_rate(baud);
    // 设置数据位
    this->set_data_bits(data);
    // 设置校验位
    this->set_parity(pari);
    // 设置停止位
    this->set_stop_bits(stop);
    // 设置数据流控制
    this->set_flow_control(flow);
    // 设置超时
    this->set_timeout(time);
    // 清空缓冲
    if (0 != tcflush(this->handle_, TCIOFLUSH))
    {
        // 发生错误，抛出异常
        throw std::system_error(std::error_code(errno, std::generic_category()), "flush error...");
    }

    return this->handle_;
}

void CSerialPort::close()
{
    // 判断串口是否打开
    if (this->is_open())
    {
        // 关闭串口
        if (::close(this->handle_))
        {
            // 发生错误，抛出异常
            throw std::system_error(std::error_code(errno, std::generic_category()), "close serial port");
        }
        this->handle_ = -1;
    }
}

bool CSerialPort::is_open() const
{
    // 判断是否打开串口
    return (this->handle_ > 2);
}

long CSerialPort::receive(void *buff, int len) const
{
    // 接收数据
    long read_bytes = ::read(this->handle_, buff, (size_t)len);

    return read_bytes;
}

void CSerialPort::send(const void * buff, long buffer_len) const
{
    ssize_t len = 0;

    do
    {
        // 发送数据，实现数据的全部发送
        ssize_t ret = ::write(this->handle_, (char*)buff + len, (size_t)(buffer_len - len));
        if (ret < 0)
        {
            // 发送数据错误
            throw std::system_error(std::error_code(errno, std::generic_category()), "send data error");
        }
        // 发送的总的数据个数
        len += ret;
    } while (len < buffer_len);
}

void CSerialPort::get_attribute(struct termios *attr) const
{
    // 取串口属性
    if (0 != tcgetattr(this->handle_, attr))
    {
        // 发生错误，抛出异常
        throw std::system_error(std::error_code(errno, std::generic_category()), "get attribute error");
    }
}

void CSerialPort::set_attribute(const struct termios *attr) const
{
    // 设置串口属性，立即生效
    if (0 != tcsetattr(this->handle_, TCSANOW, attr))
    {
        // 发生错误，抛出异常
        throw std::system_error(std::error_code(errno, std::generic_category()), "set attribute error");
    }
}

void CSerialPort::set_baud_rate(EBaudRate baud)
{
    struct termios attr{};
    speed_t i_baud;

    this->baud_rate_ = baud;
    // 取得串口属性
    this->get_attribute(&attr);
    switch (baud)
    {
        case BR1200:
            i_baud = B1200;
            break;
        case BR2400:
            i_baud = B2400;
            break;
        case BR4800:
            i_baud = B4800;
            break;
        case BR9600:
            i_baud = B9600;
            break;
        case BR19200:
            i_baud = B19200;
            break;
        case BR38400:
            i_baud = B38400;
            break;
        case BR57600:
            i_baud = B57600;
            break;
        case BR115200:
            i_baud = B115200;
            break;
        case BR230400:
            i_baud = B230400;
            break;
        case BR460800:
            i_baud = B460800;
            break;
        case BR921600:
            i_baud = B921600;
            break;
    }
    // 设置波特率
    if (0 != cfsetispeed(&attr, i_baud))
    {
        // 发生错误，抛出异常
        throw std::system_error(std::error_code(errno, std::generic_category()), "set speed error");
    }
    if (0 != cfsetospeed(&attr, i_baud))
    {
        // 发生错误，抛出异常
        throw std::system_error(std::error_code(errno, std::generic_category()), "set speed error");
    }

    // 设置串口属性
    this->set_attribute(&attr);
}

void CSerialPort::set_data_bits(EDataBits data)
{
    struct termios attr{};

    this->data_bits_ = data;
    // 取得串口属性
    this->get_attribute(&attr);
    // 设置数据位
    attr.c_cflag &= ~CSIZE;
    switch (data)
    {
        case DB5:
            attr.c_cflag |= CS5;
            break;
        case DB6:
            attr.c_cflag |= CS6;
            break;
        case DB7:
            attr.c_cflag |= CS7;
            break;
        case DB8:
            attr.c_cflag |= CS8;
            break;
    }
    // 设置串口属性
    this->set_attribute(&attr);
}

void CSerialPort::set_parity(EParity pari)
{
    struct termios attr{};

    this->parity_ = pari;
    // 取得串口属性
    this->get_attribute(&attr);
    // 设置校验位
    switch (pari)
    {
        case NoParity:
            // 不适用校验
            attr.c_cflag &= ~PARENB;
            attr.c_iflag &= ~(INPCK);// | ISTRIP
            break;
        case OddParity:
            // 使用奇校验
            attr.c_cflag |= (PARENB | PARODD);
            attr.c_iflag |= (INPCK);// 启动输入校验// | ISTRIP
            break;
        case EvenParity:
            // 使用偶校验
            attr.c_cflag |= PARENB;  // 启用校验
            attr.c_cflag &= ~PARODD; // 取出奇校验标志
            attr.c_iflag |= (INPCK);// | ISTRIP(自动取出第8位，奇偶校验时用？)
            break;
    }
    // 设置串口属性
    this->set_attribute(&attr);
}

void CSerialPort::set_stop_bits(EStopBits stop)
{
    struct termios attr{};

    this->stop_bits_ = stop;
    // 取得串口属性
    this->get_attribute(&attr);
    // 设置停止位
    switch (stop)
    {
        case OneStopBit:
            // 设置1个停止位
            attr.c_cflag &= ~CSTOPB;
            break;
        case TwoStopBits:
            // 设置2个停止位
            attr.c_cflag |= CSTOPB;
            break;
    }

    // 设置串口属性
    this->set_attribute(&attr);
}

void CSerialPort::set_flow_control(EFlowControl flow)
{
    struct termios attr{};

    this->flow_control_ = flow;
    // 取得串口属性
    this->get_attribute(&attr);
    switch (flow)
    {
        case NoFlowControl:
            // 设置无流控
            attr.c_cflag &= ~CRTSCTS; // 取消硬流控
            attr.c_iflag &= ~(IXON | IXOFF | IXANY);  // 取消软流控
            break;
        case XonXoffFlowControl:
            // 设置软流控
            attr.c_cflag &= ~CRTSCTS;
            attr.c_iflag |= (IXON | IXOFF | IXANY);
            attr.c_cc[VSTART] = 021;
            attr.c_cc[VSTOP]  = 023;
            break;
        case CtsRtsFlowControl:
            // 设置硬流控
            attr.c_cflag |= CRTSCTS;
            attr.c_iflag &= ~(IXON | IXOFF | IXANY);
            break;
    }
    // 设置串口属性
    this->set_attribute(&attr);
}

void CSerialPort::set_timeout(int time)
{
    struct termios attr{};

    this->timeout_ = time;
    // 取得串口属性
    this->get_attribute(&attr);
    // 设置超时
    if (time < 0)
    {
        // 阻塞模式，最少接收1个字符后返回
        attr.c_cc[VTIME] = 0;   // 超时
        attr.c_cc[VMIN]  = 1;   // 最小接收字符数，
    }
    else if (time == 0)
    {
        // 非阻塞，立即返回
        attr.c_cc[VTIME] = 0;
        attr.c_cc[VMIN]  = 0;
    }
    else
    {
        // 设置超时返回，接收不到数据超时后返回
        attr.c_cc[VTIME] = time;
        attr.c_cc[VMIN]  = 0;
    }
    // 设置串口属性
    this->set_attribute(&attr);
}

void CSerialPort::set_normal_mode()
{
    struct termios attr{};

    // 初始化属性结构体
    (void)memset(&attr, 0, sizeof(struct termios));
    // 接收和本地连接使能
    attr.c_cflag = CREAD | CLOCAL;
    // 设置属性
    this->set_attribute(&attr);
}
